package io.kiki.stack.http.feign.form.utils;

import io.appulse.utils.SocketUtils;
import io.undertow.Undertow;
import io.undertow.io.Receiver.FullBytesCallback;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.BlockingHandler;
import io.undertow.util.Headers;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import lombok.val;

import java.net.InetSocketAddress;

public final class UndertowServer implements AutoCloseable {

    private final Undertow undertow;

    @Builder(buildMethodName = "start")
    private UndertowServer(FullBytesCallback callback) {
        val port = SocketUtils.findFreePort().orElseThrow(() -> new IllegalStateException("no available port to start server"));

        undertow = Undertow.builder().addHttpListener(port, "localhost").setHandler(new BlockingHandler(new ReadAllBytesHandler(callback))).build();

        undertow.start();
    }

    /**
     * Returns server connect URL.
     *
     * @return listining server's url.
     */
    public String getConnectUrl() {
        val listenerInfo = undertow.getListenerInfo().iterator().next();

        val address = (InetSocketAddress) listenerInfo.getAddress();

        return String.format("%s://%s:%d", listenerInfo.getProtcol(), address.getHostString(), address.getPort());
    }

    @Override
    public void close() {
        undertow.stop();
    }

    @RequiredArgsConstructor
    private static final class ReadAllBytesHandler implements HttpHandler {

        private final FullBytesCallback callback;

        @Override
        public void handleRequest(HttpServerExchange exchange) throws Exception {
            exchange.getRequestReceiver().receiveFullBytes(this::handleBytes);
        }

        private void handleBytes(HttpServerExchange exchange, byte[] message) {
            try {
                callback.handle(exchange, message);
            } catch (Throwable ex) {
                exchange.setStatusCode(500);
                exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                exchange.getResponseSender().send(ex.getMessage());
            }
        }
    }
}
